package com.dcivision.webdav.util;

import java.net.MalformedURLException;

/**
 * This is the generic URI(Uniform Resource Identifiers) version of RFC 2396.
 *
 * Generic-URI = <scheme>://<authority><path>?<query>
 *
 * It's assumed that URI is escaped by URI encoding and processed.
 * Care should be taken when a URL contains escaped delimiters for a
 * given protocol that these are not unescaped before transmission.
 * This might violate the protocol, but avoids the potential for such
 * characters to be used to simulate an extra operation or parameter in
 * that protocol, which might lead to an unexpected and possibly harmful
 * remote operation to be performed.
 *
 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
 */
public abstract class GenericURI
    implements java.io.Serializable {

  // --------------------------------------------------------- Constructors

  /**
   * This Constructor
   *
   * @param escapedURI The escaped URI string.
   */
  public GenericURI(String escapedURI) {
    URI = escapedURI;
  }

  /**
   * This Constructor
   *
   * @param scheme The scheme string.
   * @param host The host string.
   * @param port The port number.
   */
  public GenericURI(String scheme, String host, int port) {
    this(scheme, host, port, null, null);
  }

  /**
   * This Constructor
   *
   * @param scheme The scheme string.
   * @param host The host string.
   * @param path The path string.
   */
  public GenericURI(String scheme, String host, String path) {
    this(scheme, host, -1, path, null);
  }

  /**
   * This Constructor
   *
   * @param scheme The scheme string.
   * @param host The host string.
   * @param port The port number.
   * @param path The path string.
   */
  public GenericURI(String scheme, String host, int port, String path) {
    this(scheme, host, port, path, null);
  }

  /**
   * This Constructor
   *
   * @param scheme The scheme string.
   * @param host The host string.
   * @param path The path string.
   * @param query The query string.
   */
  public GenericURI(String scheme, String host, String path, String query) {
    this(scheme, host, -1, path, null);
  }

  /**
   * This Constructor
   *
   * @param scheme The scheme string.
   * @param host The host string.
   * @param port The port number.
   * @param path The path string.
   * @param query The query string.
   */
  public GenericURI(String scheme, String host, int port, String path,
                    String query) {

    URI = URIUtil.escape(scheme, URIUtil.schemeReserved()) + "://" +
        URIUtil.escape(host, URIUtil.hostReserved()) +
        ( (port == defaultPort || port == -1) ? "" : ":" + port) +
        ( (path == null || path.equals("")) ? "/" :
         URIUtil.escape( (!path.startsWith("/")) ?
                        "/" + path : path, URIUtil.pathReserved())) +
        ( (query == null || query.equals("")) ? "" :
         "?" + URIUtil.escape(query, URIUtil.queryReserved()));
  }

  // --------------------------------------------------- Instance Variables

  /**
   * This Uniform Resource Identifier (URI).
   * The URI to be escaped must be saved and processed.
   */
  private String URI;

  /**
   * The default scheme is unset.
   * IMPORTANT: need to set at the subclass.
   *
   * @see #setDefaultScheme(java.lang.String)
   */
  private String defaultScheme = null;

  /**
   * The default port is unset.
   * IMPORTANT: need to set at the subclass.
   *
   * @see #setDefaultPort(int)
   */
  private int defaultPort = -1;

  // ----------------------------------------------------------- Properties

  /**
   * Get the escaped URI string.
   */
  public String getEscapedURI() {
    return URI;
  }

  /**
   * Set the escaped URI string.
   */
  protected void setEscapedURI(String escapedURI) {
    URI = escapedURI;
  }

  /**
   * Get the URI string.
   */
  public String getURI() {
    return URIUtil.unescape(URI);
  }

  /**
   * This is an wrapper method for java.net.URL
   *
   * @return The external form of this URI.
   */
  public String toExternalForm() {
    return getEscapedURI();
  }

  // ---------------------------------------------------  Protected methods

  /**
   * Set the scheme for this Generic URI.
   *
   * @param scheme The scheme for this Generic URI.
   */
  protected void setDefaultScheme(String scheme) {
    defaultScheme = scheme.toLowerCase();
  }

  /**
   * Get the scheme for this Generic URI.
   *
   * @return The scheme for this Generic URI.
   */
  protected abstract String getDefaultScheme();

  /**
   * Set the default port number for this generic URI.
   *
   * @param port The port number to set for this generic URI.
   */
  protected void setDefaultPort(int port) {
    defaultPort = port;
  }

  /**
   * Get the default port number for this generic URI.
   *
   * @return The port number to set for this generic URI.
   */
  protected abstract int getDefaultPort();

  // ------------------------------------------------------- Public methods

  /**
   * Get the scheme for this Generic URI.
   *
   * @return The scheme for this Generic URI.
   * @exception MalformedURLException no scheme.
   */
  public String getScheme() throws MalformedURLException {

    return getScheme(URI);
  }

  /**
   * Get the escaped scheme for this Generic URI.
   *
   * @return The escaped scheme for this Generic URI.
   * @exception MalformedURLException no scheme.
   */
  public String getEscapedScheme() throws MalformedURLException {

    return getEscapedScheme(URI);
  }

  /**
   * Get the scheme of the given URI string.
   *
   * @param escapedURI The escaped URI string to get the scheme.
   * @return The scheme of the given URI.
   * @exception MalformedURLException no scheme.
   */
  public String getScheme(String escapedURI) throws MalformedURLException {

    return getScheme(escapedURI, defaultScheme);
  }

  /**
   * Get the escaped scheme of the given URI string.
   *
   * @param  escapedURI The escaped URI string to get the scheme.
   * @return The escaped scheme of the given URI.
   * @exception MalformedURLException no scheme.
   */
  public String getEscapedScheme(String escapedURI) throws
      MalformedURLException {

    return getEscapedScheme(escapedURI, defaultScheme);
  }

  /**
   * Get the scheme of the given URI string.
   *
   * @param escapedURI The URI string to get the scheme.
   * @param defaultScheme The scheme to set.
   * @return The scheme of the given URI.
   * @exception MalformedURLException no scheme.
   */
  public static String getScheme(String escapedURI,
                                 String defaultScheme) throws
      MalformedURLException {

    return URIUtil.unescape(getEscapedScheme(escapedURI, defaultScheme));
  }

  /**
   * Get the escaped scheme of the given URI string.
   *
   * @param escapedURI The URI string to get the scheme.
   * @param defaultScheme The scheme to set.
   * @return The escaped scheme of the given URI.
   * @exception MalformedURLException no scheme.
   */
  public static String getEscapedScheme(String escapedURI,
                                        String defaultScheme) throws
      MalformedURLException {

    int at = escapedURI.indexOf("://");
    if (at > 0) {
      return escapedURI.substring(0, at);
    }

    // Check the default.
    if (defaultScheme == null) {
      throw new MalformedURLException("No default scheme: "
                                      + escapedURI);
    }

    return defaultScheme;
  }

  /**
   * Get the authority part for this generic URI.
   *
   * @return The authority part of this generic URI.
   * @exception MalformedURLException
   */
  public String getAuthority() throws MalformedURLException {

    return getAuthority(URI);
  }

  /**
   * Get the escaped authority part for this generic URI.
   *
   * @return The escaped authority part of this generic URI.
   * @exception MalformedURLException
   */
  public String getEscapedAuthority() throws MalformedURLException {

    return getEscapedAuthority(URI);
  }

  /**
   * Get the authority part of the given escaped URI string.
   *
   * @param escapedURI The escaped URI string to get the authority part.
   * @return The authority part of the given URI.
   * @exception MalformedURLException
   */
  public static String getAuthority(String escapedURI) throws
      MalformedURLException {

    return URIUtil.unescape(getEscapedAuthority(escapedURI));
  }

  /**
   * Get the escaped authority part of the given escaped URI string.
   *
   * @param escapedURI The escaped URI string to get the authority part.
   * @return The escaped authority part of the given URI.
   * @exception MalformedURLException
   */
  public static String getEscapedAuthority(String escapedURI) throws
      MalformedURLException {

    int at = escapedURI.indexOf("://");
    if (at > 0) {
      int from = at + 3;
      int to = escapedURI.indexOf("/", from);

      return (to > 0) ? escapedURI.substring(from, to) :
          escapedURI.substring(from);
    }
    else {
      throw new MalformedURLException("No scheme: " + escapedURI);
    }
  }

  /**
   * Get the hostport part for this generic URI.
   *
   * @return The hostport string
   * @exception MalformedURLException
   */
  public String getHostPort() throws MalformedURLException {

    return getHostPort(URI);
  }

  /**
   * Get the escaped hostport part for this generic URI.
   *
   * @return The escaped hostport string
   * @exception MalformedURLException
   */
  public String getEscapedHostPort() throws MalformedURLException {

    return getEscapedHostPort(URI);
  }

  /**
   * Get the hostport part of the given URI string.
   *
   * @param escapedURI The escaped URI string to get the hostport part.
   * @return The hostport
   * @exception MalformedURLException
   */
  public static String getHostPort(String escapedURI) throws
      MalformedURLException {

    return URIUtil.unescape(getEscapedHostPort(escapedURI));
  }

  /**
   * Get the escaped hostport part of the given URI string.
   *
   * @param escapedURI The escaped URI string to get the hostport part.
   * @return The hostport
   * @exception MalformedURLException
   */
  public static String getEscapedHostPort(String escapedURI) throws
      MalformedURLException {

    String authority = getEscapedAuthority(escapedURI);
    int at = authority.lastIndexOf("@");

    return (at > 0) ? authority.substring(at + 1) : authority;
  }

  /**
   * Get the hostname for this generic URI.
   *
   * @return The hostname string
   * @exception MalformedURLException
   */
  public String getHost() throws MalformedURLException {

    return getHost(URI);
  }

  /**
   * Get the escaped hostname for this generic URI.
   *
   * @return The escaped hostname string
   * @exception MalformedURLException
   */
  public String getEscapedHost() throws MalformedURLException {

    return getEscapedHost(URI);
  }

  /**
   * Get the hostname of the given URI string.
   *
   * @param escapedURI The escaped URI string to get the hostname.
   * @return The hostname string
   * @exception MalformedURLException
   */
  public static String getHost(String escapedURI) throws MalformedURLException {

    return URIUtil.unescape(getEscapedHost(escapedURI));
  }

  /**
   * Get the escaped hostname of the given URI string.
   *
   * @param escapedURI The escaped URI string to get the hostname.
   * @return The escaped hostname string
   * @exception MalformedURLException
   */
  public static String getEscapedHost(String escapedURI) throws
      MalformedURLException {

    String hostPort = getEscapedHostPort(escapedURI);
    int to = hostPort.lastIndexOf(":");

    return (to > 0) ? hostPort.substring(0, to) : hostPort;
  }

  /**
   * Get the port number for this generic URI.
   *
   * @return The port number
   * @exception MalformedURLException
   */
  public int getPort() throws MalformedURLException {

    return getPort(URI);
  }

  /**
   * Get the port number for this generic URI.
   *
   * @param escapedURI The escaped URI string to get the port number.
   * @return The port number
   * @exception MalformedURLException
   */
  public int getPort(String escapedURI) throws MalformedURLException {

    return getPort(escapedURI, defaultPort);
  }

  /**
   * Get the port number of the given URI string.
   *
   * @param escapedURI The escaped URI string to get the port number.
   * @param defaultPort The default port for the given URI.
   * @return The port number
   * @exception MalformedURLException
   */
  public static int getPort(String escapedURI, int defaultPort) throws
      MalformedURLException {

    String hostPort = getEscapedHostPort(escapedURI);
    int at = hostPort.lastIndexOf(":");
    if (at > 0) {
      String port = URIUtil.unescape(hostPort.substring(at + 1));
      try {
        // Support the form like http://thinkfree.com:/~jericho
        return port.equals("") ? defaultPort : Integer.parseInt(port);
      }
      catch (NumberFormatException nfe) {
        // The wrong port or unset number returns -1.
        return -1;
      }
    }

    if (defaultPort == -1) {
      throw new MalformedURLException("No default port: " + escapedURI);
    }

    // The default port number.
    return defaultPort;
  }

  /**
   * Get the net_path and query of the given escaped URI string.
   * The methods returns the net_loc and abs_path.
   *
   * @return The net_path and query.
   */
  public String getNetPathQuery() {
    return getNetPathQuery(URI);
  }

  /**
   * Get the escaped net_path and query of the given escaped URI string.
   * The methods returns the net_loc and abs_path.
   *
   * @return The escaped net_path and query.
   */
  public String getEscapedNetPathQuery() {
    return getEscapedNetPathQuery(URI);
  }

  /**
   * Get the net_path and query of the given escaped URI string.
   * The methods returns the net_loc and abs_path.
   *
   * @param escapedURI The escaped URI string.
   * @return The net_path and query.
   */
  public static String getNetPathQuery(String escapedURI) {
    return URIUtil.unescape(getEscapedNetPathQuery(escapedURI));
  }

  /**
   * Get the net_path and query of the given escaped URI string.
   * The methods returns the net_loc and abs_path.
   *
   * @param escapedURI The escaped URI string.
   * @return The escaped net_path and query.
   */
  public static String getEscapedNetPathQuery
      (String escapedURI) {

    // consider of net_path
    int from = escapedURI.indexOf("//");
    // Ignore the authority part of URI
    int to = escapedURI.length();
    // Ignore the '?' mark so to ignore the query.
    // check the fragment
    if (escapedURI.lastIndexOf("#") > from) {
      to = escapedURI.lastIndexOf("#");
      // get only the path.
    }
    return (from >= 0) ? escapedURI.substring(from, to) : null;
  }

  /**
   * Get the abs_path or rel_path, and query for this generic URI.
   * This method just ignores the scheme and authority part of the URI path.
   * So this method doesn't throw any exception.
   *
   * @return The abs_path or rel_path, and query.
   */
  public String getPathQuery() {
    return getPathQuery(URI);
  }

  /**
   * Get the escaped abs_path or rel_path, and query for this generic URI.
   * This method just ignores the scheme and authority part of the URI path.
   * So this method doesn't throw any exception.
   *
   * @return The escaped abs_path or rel_path, and query.
   */
  public String getEscapedPathQuery() {
    return getEscapedPathQuery(URI);
  }

  /**
   * Get the abs_path or rel_path, and query of the escaped given URI string.
   * This method just ignores the scheme and authority part of the URI path.
   * So this method doesn't throw any exception.
   *
   * @param escapedURI The escaped URI string.
   * @return The abs_path or rel_path, and query.
   */
  public static String getPathQuery(String escapedURI) {
    return URIUtil.unescape(getEscapedPathQuery(escapedURI));
  }

  /**
   * Get the escaped abs_path or rel_path, and query of the escaped given
   * URI string.  This method just ignores the scheme and authority part of
   * the URI path. So this method doesn't throw any exception.
   *
   * @param escapedURI The escaped URI string.
   * @return The escaped abs_path or rel_path, and query.
   */
  public static String getEscapedPathQuery(String escapedURI) {

    // consider of net_path
    int at = escapedURI.indexOf("//");
    int from = escapedURI.indexOf("/", (at >= 0) ? at + 2 : 0);
    // Ignore the authority part of URI
    int to = escapedURI.length();
    // Ignore the '?' mark so to ignore the query.
    // check the fragment
    if (escapedURI.lastIndexOf("#") > from) {
      to = escapedURI.lastIndexOf("#");
      // get only the wanted path.
    }
    return (from >= 0) ? escapedURI.substring(from, to) : "/";
  }

  /**
   * Set the path for this generic URI.
   *
   * @param path The specified path.
   * @exception MalformedURLException
   */
  public void setPath(String path) throws MalformedURLException {

    if (!path.startsWith("/")) {
      path = "/" + path;
    }
    URI = getEscapedScheme() + "://" + getEscapedAuthority()
        + URIUtil.escape(path, URIUtil.pathReserved());
  }

  /**
   * Get the path for this generic URI.
   * This method ignores the scheme and authority part of the URI path.
   *
   * @return The path.
   */
  public String getPath() {
    return getPath(URI);
  }

  /**
   * Get the escaped path for this generic URI.
   * This method ignores the scheme and authority part of the URI path.
   *
   * @return The escaped path.
   */
  public String getEscapedPath() {
    return getEscapedPath(URI);
  }

  /**
   * Get the path of the given escaped URI string.
   * This method ignores the scheme and authority part of the URI path.
   *
   * @param escapedURI The specified escaped URI string.
   * @return The path.
   */
  public static String getPath(String escapedURI) {
    return URIUtil.unescape(getEscapedPath(escapedURI));
  }

  /**
   * Get the path of the given escaped URI string.
   * This method ignores the scheme and authority part of the URI path.
   *
   * @param escapedURI The specified escaped URI string.
   * @return The escaped path.
   */
  public static String getEscapedPath(String escapedURI) {

    // consider of net_path
    int at = escapedURI.indexOf("//");
    int from = escapedURI.indexOf("/", (at >= 0) ? at + 2 : 0);
    // Ignore the authority part of URI
    int to = escapedURI.length();
    // check the query
    if (escapedURI.lastIndexOf("?") > from) {
      to = escapedURI.lastIndexOf("?");
      // check the fragment
    }
    if (escapedURI.lastIndexOf("#") > from &&
        escapedURI.lastIndexOf("#") < to) {
      to = escapedURI.lastIndexOf("#");
      // get only the path.
    }
    return (from >= 0) ? escapedURI.substring(from, to) : "/";
  }

  /**
   * Get the query for this generic URI.
   * This method ignores the scheme and authority part for this generic URI.
   *
   * @return The query string.
   * @exception MalformedURLException
   */
  public String getQuery() throws MalformedURLException {

    return getQuery(URI);
  }

  /**
   * Get the escaped query for this generic URI.
   * This method ignores the scheme and authority part for this generic URI.
   *
   * @return The escaped query string.
   * @exception MalformedURLException
   */
  public String getEscapedQuery() throws MalformedURLException {

    return getEscapedQuery(URI);
  }

  /**
   * Get the query of the given URI string.
   * This method ignores the scheme and authority part of the given URI.
   *
   * @param escapedURI The escaped URI string.
   *                   It could be the generic URI and relative URI.
   * @return The query string.
   * @exception MalformedURLException
   */
  public static String getQuery(String escapedURI) throws MalformedURLException {

    return URIUtil.unescape(getEscapedQuery(escapedURI));
  }

  /**
   * Get the escaped query of the given URI string.
   * This method ignores the scheme and authority part of the given URI.
   *
   * @param escapedURI The escaped URI string.
   *                   It could be the generic URI and relative URI.
   * @return The escaped query string.
   * @exception MalformedURLException
   */
  public static String getEscapedQuery(String escapedURI) throws
      MalformedURLException {

    int at = escapedURI.indexOf("//");
    // Just ignore the authority part of URI
    at = (at > 0) ? escapedURI.indexOf("/", at + 2) :
        escapedURI.indexOf("/");
    if (at > 0) {
      // check the query
      int from = (escapedURI.lastIndexOf("?", at) > 0) ?
          escapedURI.lastIndexOf("?") + 1 : -1;
      int to = escapedURI.length();
      // check the fragment
      if (escapedURI.lastIndexOf("#", at) > at &&
          escapedURI.lastIndexOf("#", at) < to) {
        to = escapedURI.lastIndexOf("#");
      }
      return (from > 0) ? escapedURI.substring(from, to) : null;
    }
    else {
      throw new MalformedURLException("Need to have the path part");
    }
  }

  /**
   * Get the fragment for this generic URI.
   * This method ignores the scheme and authority part of the given URI.
   *
   * @return The fragment string.
   * @exception MalformedURLException
   */
  public String getFragment() throws MalformedURLException {

    return getFragment(URI);
  }

  /**
   * Get the escaped fragment for this generic URI.
   * This method ignores the scheme and authority part of the given URI.
   *
   * @return The escaped fragment string.
   * @exception MalformedURLException
   */
  public String getEscapedFragment() throws MalformedURLException {

    return getEscapedFragment(URI);
  }

  /**
   * Get the fragment of the given escaped URI string.
   * This method ignores the scheme and authority part of the given URI.
   *
   * @param escapedURI The escaped URI string.
   *                   It could be the generic URI and relative URI.
   * @return The fragment string.
   * @exception MalformedURLException
   */
  public static String getFragment(String escapedURI) throws
      MalformedURLException {

    return URIUtil.unescape(getEscapedFragment(escapedURI));
  }

  /**
   * Get the fragment of the given escaped URI string.
   * This method ignores the scheme and authority part of the given URI.
   *
   * @param escapedURI The escaped URI string.
   *                   It could be the generic URI and relative URI.
   * @return The escaped fragment string.
   * @exception MalformedURLException
   */
  public static String getEscapedFragment(String escapedURI) throws
      MalformedURLException {

    int at = escapedURI.indexOf("//");
    // Just ignore the authority part of URI
    at = (at > 0) ? escapedURI.indexOf("/", at + 2) :
        escapedURI.indexOf("/");
    if (at > 0) {
      // check the fragment
      int from = (escapedURI.lastIndexOf("#", at) > 0) ?
          escapedURI.lastIndexOf("#", at) + 1 : -1;
      return (from > 0) ? escapedURI.substring(from) : null;
    }
    else {
      throw new MalformedURLException("Need to have the path part");
    }
  }

  // ------------------------------------------  Methods for basic function

  /**
   * Test a object if this is equal with another.
   *
   * @param obj An object to compare.
   * @return true if two URI objects are equal.
   */
  public boolean equals(Object obj) {

    if ( (obj != null) && (obj instanceof GenericURI)) {

      try {
        GenericURI URI = (GenericURI) obj;
        if (getEscapedScheme().equalsIgnoreCase
            (URI.getEscapedScheme()) &&
            getEscapedHost().equalsIgnoreCase
            (URI.getEscapedHost()) &&
            getPort() == URI.getPort() &&
            getEscapedPathQuery().equals
            (URI.getEscapedPathQuery())) {

          return true;
        }
      }
      catch (MalformedURLException ignored) {
      }
    }

    return false;
  }

  /**
   * Get the escaped URI string.
   *
   * @return The URI string.
   */
  public String toString() {
    return getEscapedURI();
  }

}
